package br.unb.statistic.distribution;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.HashMap;
import br.unb.utils.LogFile;
import br.unb.statistic.RandomGenerator;
import br.unb.statistic.SpectrumProcess;
import br.unb.utils.LogPrinter;

// TODO Verify Keys used on XML and XML generator and generate a xml file
// to build the XmlFrame to hold the parameters, (no frame generated for the combo element yet!)
public class SelfSimilarFGNGenerator extends PacketsPerIntervalGenerator {

    //Parameters
    private int numSamples;
    private double hurst;
    private double variance;
    private double mean;
    private RandomGenerator rG;
    private SpectrumProcess sP;
    //File
    private LogFile simulationFile;
    private LogPrinter simulationPrinter;
    private LineNumberReader lnr;

    public SelfSimilarFGNGenerator() {
    }

    public SelfSimilarFGNGenerator(String name, double mean, double variance, double hurst, long samples, double interval) throws IOException {
        initGen(name, mean, variance, hurst, samples, interval);
    }

    public SelfSimilarFGNGenerator(String name, double mean, double variance, double hurst, long samples) throws IOException {
        initGen(name, mean, variance, hurst, samples, 1);
    }

    private void initGen(String name, double mean, double variance, double hurst, long samples, double interval) throws IOException {

        //Parameters
        this.hurst = hurst;
        this.mean = mean;
        this.interval = interval;
        this.numSamples = (int) Math.pow(2, Math.ceil(Math.log(samples) / Math.log(2)));
        this.variance = variance;
        this.sP = new SpectrumProcess(this.hurst);
        //File
        this.simulationFile = new LogFile(LogFile.Type.SIMULATE, name);
        this.simulationPrinter = new LogPrinter(simulationFile);
        this.lnr = new LineNumberReader(new InputStreamReader(new FileInputStream(this.simulationFile)));

        simulation();
    }

    private synchronized void simulation() throws IOException {
        double seq[] = this.fgnSequence();
        for (int i = 0; i < this.numSamples; i++) {
            this.simulationPrinter.write( Double.toString((double) seq[2 * i]));
        }
        this.simulationPrinter.close();
    }

    public synchronized double rand() {
        try {
            return Double.parseDouble(lnr.readLine());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Double.NaN;
    }

    private void fuzz(double PowSpectrum[]) {
        rG = new ExponentialDistribution(1);

        for (int i = 0; i <= this.numSamples / 2; i++) {
            PowSpectrum[i] *= rG.rand();
        }
    }

    private double[] zSequence(double PowSpectrum[]) {

        double z[] = new double[2 * this.numSamples];
        z[0] = 0;
        z[1] = 0;

        for (int i = 1; i <= this.numSamples / 2; i++) {
            z[2 * i] = Math.sqrt(PowSpectrum[i]);
            z[2 * i + 1] = 2 * Math.PI * RandomGenerator.random.nextDouble();
        }

        for (int i = (this.numSamples / 2 + 1); i < this.numSamples; i++) {
            z[2 * i] = z[2 * (this.numSamples - i)];
            z[2 * i + 1] = -z[2 * (this.numSamples - i) + 1];
        }

        double re;
        double im;

        for (int i = 0; i < this.numSamples; i++) {
            re = z[2 * i] * Math.cos(z[2 * i + 1]);
            im = z[2 * i] * Math.sin(z[2 * i + 1]);
            z[2 * i] = re;
            z[2 * i + 1] = im;
        }

        return z;
    }

    public double[] fgnSequence() throws IOException {
        double spec[];
        double seq[];
        spec = sP.fgnSpectrum(this.numSamples);
        this.fuzz(spec);
        seq = this.zSequence(spec);
        seq = SpectrumProcess.fft(seq, this.numSamples, -1);
        for (int i = 0; i < this.numSamples; i++) {
            seq[2 * i] /= this.numSamples;
        }

        adjustVar(seq);
        adjustMean(seq);

        /*double err = 0;
        for(int i = 0; i < this.numSamples; i++){
        seq[2 * i] += err;
        err = seq[2 * i];
        seq[2 * i] = Math.rint(seq[2 * i]);
        err -= seq[2 * i];
        }

        for(int i = 0; i < this.numSamples; i++){
        if(seq[2 * i] < 0.0D)
        seq[2 * i] = 0.0D;
        }*/

        return seq;
    }

    private void adjustMean(double seq[]) {
        double media;
        media = sequenceMean(seq);
        double dif = this.mean - media;
        for (int i = 0; i < this.numSamples; i++) {
            seq[2 * i] = seq[2 * i] + dif;
        }

    }

    private void adjustVar(double seq[]) {
        double var;
        var = sequenceVar(seq);
        double factor = Math.sqrt(this.variance / var);

        for (int i = 0; i < this.numSamples; i++) {
            seq[2 * i] = factor * seq[2 * i];
        }
    }

    private double sequenceVar(double seq[]) {
        double variance = 0;
        double media;
        media = sequenceMean(seq);
        for (int i = 0; i < this.numSamples; i++) {
            variance += (seq[2 * i] - media) * (seq[2 * i] - media);
        }

        variance /= this.numSamples;
        return variance;
    }

    private double sequenceMean(double seq[]) {
        double media = 0;
        for (int i = 0; i < this.numSamples; i++) {
            media += seq[2 * i];
        }

        media /= this.numSamples;
        return media;
    }

    public double getHurst() {
        return hurst;
    }

    public double getMean() {
        return mean;
    }

    public int getNumSamples() {
        return numSamples;
    }

    public double getVariance() {
        return variance;
    }

    public void finalize() {
        try {
            lnr.close();
            simulationPrinter.close();
            //simulationFile.delete();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    public String xml() {
        String xml = "<SELFSIMILARFGN>"
                + "<MEAN>" + this.getMean() + "</MEAN>"
                + "<VARIANCE>" + getVariance() + "</VARIANCE>"
                + "<HURST>" + getHurst() + "</HURST>"
                + "</SELFSIMILARFGN>";
        return xml;
    }

    public void setParameters(HashMap parameters) {
        String name;
        double hurst, interval, mean, variance;
        long samples;
        hurst = java.lang.Double.parseDouble((String) parameters.get("hurst"));
        if (parameters.get("name") != null) {
            name = (String) parameters.get("name");
        } else {
            name = "selfSimilar";
        }
        if (parameters.get("interval") != null) {
            interval = java.lang.Double.parseDouble((String) parameters.get("interval"));
            mean = java.lang.Double.parseDouble((String) parameters.get("mean"));
            variance = java.lang.Double.parseDouble((String) parameters.get("variance"));
        } else {
            interval = 0.1;
            double rate = java.lang.Double.parseDouble((String) parameters.get("rate"));
            mean = rate * interval;
            variance = mean * mean / 4;
        }
        if (parameters.get("samples") != null) {
            samples = java.lang.Long.parseLong((String) parameters.get("samples"));
        } else if (parameters.get("duration") != null) {
            double duration = java.lang.Double.parseDouble((String) parameters.get("duration"));
            samples = Math.round(duration / interval);
        } else {
            double numPackets = java.lang.Double.parseDouble((String) parameters.get("numPackets"));
            samples = (long) ((numPackets / mean) * 1.1);
        }
        try {
            initGen(name, mean, variance, hurst, samples, interval);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    /* //Old code with the testing unit and stuff YO!!!

    public static void main(String args[]) {
    try {
    for (int i = 0; i < 20; i++) {
    SelfSimilarFGNGenerator fgn = new SelfSimilarFGNGenerator("autosimilarteste", 10, 40, 1, 1024);
    }
    System.out.println("Pronto");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }*/
}
